home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Mac / Contrib / IDE mods / SpaceIndenting / PySpaceEditor.py
Encoding:
Python Source  |  2000-06-23  |  6.8 KB  |  198 lines

  1. # Module PySpaceEditor, alpha 5/21/99
  2. # by Oliver Steele (steele@cs.brandeis.edu).
  3.  
  4. """ Module PySpaceEditor
  5.  
  6. This is a work in progress.  It's alpha software: use it at your own risk.
  7.  
  8. Importing this module into PythonIDE (the MacOS Python IDE) changes the behavior of
  9. text windows such that the Tab key inserts four spaces, instead of a tab character.
  10. It also changes "Shift left" and "Shift right" to recognize and insert, respectively, spaces
  11. instead of tabs.  These changes make PythonIDE compatible with the behavior of emacs'
  12. python-mode, and with IDLE; they're useful if you're editing code that was developed on
  13. or is shared with developers on other platforms.
  14.  
  15. For compatibility with hard-tabbed files, the system recognizes files that contain a
  16. carriage return followed by a tab ('\r\t'), and uses hard tabs instead of spaces in windows
  17. opened on those files.   The behavior of the patched system is intended to be identical
  18. to the behavior of the unpatched system in this case, except that with the patch, the
  19. editor recognizes a sequence of spaces -- even in a window marked as hard-tabbed --
  20. as being equivalent to a level of indentation.
  21.  
  22. At some point it should become possible to set this behavior (hard tab versus soft tab)
  23. on a window-by-window basis, and to change the default.  For now, you can type:
  24.     >>> PySpaceEditor.PySpaceEditor.use_hard_tabs = 1
  25. to cause new files to use hard tabs.
  26.  
  27. Usage:  After PythonIDE has launched, type 'import PySpaceEditor' at its command line.
  28. """
  29.  
  30. # To do:
  31. # - add UI for setting default, file to use hard vs. soft tabs
  32. # - add UI for setting soft tab indentation?  maybe this should just be hardwired to 4
  33. # - Generalize references to '\t' to accept soft tabs too:
  34. #   - PyEdit.Editor._runselection
  35. #   - PyEdit.getminindent
  36. #   - Wtext.indentPat; this is used to recognize comments
  37. #   - Wtext.PyEditor.click
  38. # Notes:
  39. # - WASTE documentation is at http://www.cs.dartmouth.edu/~ngm/waste/reference.html
  40.  
  41. import Qd
  42. import Res
  43. import Events
  44. import string
  45. import Wkeys
  46. import PyFontify
  47. import Wtext
  48. import W
  49. from Wtext import GetPortFontSettings, SetPortFontSettings
  50.  
  51. # Implementation note: in order to make developing this easier, and to make it work as a patch,
  52. # we define a subclass of (the original) Wtext.PyEditor, and rebind Wtext.PyEditor and
  53. # W.PyEditor to that subclass.  It's intended that when this code is complete, the methods
  54. # in the PySpaceEditor class could supplement and replace the corresponding methods in
  55. # PyEditor, and this would cease to exist as a separate file and class.
  56.  
  57. PyEditor = globals().get("PyEditor", Wtext.PyEditor)
  58.  
  59. class PySpaceEditor(PyEditor):
  60.     indent_offset = 4    # spaces per program indentation level
  61.     use_hard_tabs = 0    # false to indent with indent_offset spaces; true to use '\t'
  62.     
  63.     def __init__(*args, **keys):
  64.         self = args[0]
  65.         apply(PyEditor.__init__, args, keys)
  66.         if string.find(self.get(), '\r\t') >= 0:
  67.             self.use_hard_tabs = 1
  68.     
  69.     def get_selection(self):
  70.         selstart, selend = self.ted.WEGetSelection()
  71.         selstart, selend = min(selstart, selend), max(selstart, selend)
  72.         return selstart, selend
  73.     
  74.     def currentline(self):
  75.         selstart, selend = self.get_selection()
  76.         pos, dummy = self.ted.WEFindLine(selstart, 0)
  77.         lineres = Res.Resource('')
  78.         self.ted.WECopyRange(pos, selstart, lineres, None, None)
  79.         return lineres.data
  80.     
  81.     def get_indentation(self, line, allcolumns=0):
  82.         columncount = 0
  83.         tabwidth = self.tab_width_in_spaces()
  84.         for c in line:
  85.             if c == ' ':
  86.                 columncount = columncount + 1
  87.             elif c == '\t':
  88.                 columncount = (columncount + tabwidth) / tabwidth * tabwidth
  89.             elif not allcolumns:
  90.                 break
  91.         if self.use_hard_tabs:
  92.             tab_width = self.tab_width_in_spaces()
  93.         else:
  94.             tab_width = self.indent_offset
  95.         tabcount = columncount / tab_width
  96.         return tabcount
  97.     
  98.     def tab_width_in_spaces(self):
  99.         tabsize, tabmode = self.tabsettings
  100.         if not tabmode:
  101.             (font, style, size, color) = self.getfontsettings()
  102.             port = self._parentwindow.wid.GetWindowPort()
  103.             savesettings = GetPortFontSettings(port)
  104.             SetPortFontSettings(port, (font, style, size))
  105.             tabsize = Qd.StringWidth(' ' * tabsize)
  106.             SetPortFontSettings(port, savesettings)
  107.         return tabsize
  108.     
  109.     def tabbing(self, tabcount):
  110.         if self.use_hard_tabs:
  111.             return '\t' * tabcount
  112.         else:
  113.             return ' ' * tabcount * self.indent_offset
  114.     
  115.     def key(self, char, event):
  116.         (what, message, when, where, modifiers) = event
  117.         if modifiers & Events.cmdKey and not char in Wkeys.arrowkeys:
  118.             return
  119.         if char == '\r':
  120.             selstart, selend = self.get_selection()
  121.             lastchar = chr(self.ted.WEGetChar(selstart-1))
  122.             if lastchar <> '\r' and selstart:
  123.                 line = self.currentline()
  124.                 tabcount = self.extratabs(line)
  125.                 self.insert('\r' + self.tabbing(tabcount))
  126.             else:
  127.                 self.ted.WEKey(ord('\r'), 0)
  128.         elif char == '\t':
  129.             if self.use_hard_tabs:
  130.                 self.ted.WEKey(ord(char), 0)
  131.             else:
  132.                 line = self.currentline()
  133.                 tabwidth = self.tab_width_in_spaces()
  134.                 column = self.get_indentation(line, 1)
  135.                 newcolumn = (column + tabwidth) / tabwidth * tabwidth
  136.                 self.insert(' ' * (newcolumn - column))
  137.         elif char in ')]}':
  138.             self.ted.WEKey(ord(char), modifiers)
  139.             self.balanceparens(char)
  140.         else:
  141.             self.ted.WEKey(ord(char), modifiers)
  142.         if char not in Wkeys.navigationkeys:
  143.             self.changed = 1
  144.         self.selchanged = 1
  145.         self.updatescrollbars()
  146.     
  147.     def extratabs(self, line):
  148.         tabcount = self.get_indentation(line)
  149.         last = 0
  150.         cleanline = ''
  151.         tags = PyFontify.fontify(line)
  152.         # strip comments and strings
  153.         for tag, start, end, sublist in tags:
  154.             if tag in ('string', 'comment'):
  155.                 cleanline = cleanline + line[last:start]
  156.                 last = end
  157.         cleanline = cleanline + line[last:]
  158.         cleanline = string.strip(cleanline)
  159.         if cleanline and cleanline[-1] == ':':
  160.             tabcount = tabcount + 1
  161.         else:
  162.             # extra indent after unbalanced (, [ or {
  163.             for open, close in (('(', ')'), ('[', ']'), ('{', '}')):
  164.                 count = string.count(cleanline, open)
  165.                 if count and count > string.count(cleanline, close):
  166.                     tabcount = tabcount + 2
  167.                     break
  168.         return tabcount
  169.     
  170.     def domenu_shiftleft(self):
  171.         self.expandselection()
  172.         selstart, selend = self.get_selection()
  173.         snippet = self.getselectedtext()
  174.         lines = string.split(snippet, '\r')
  175.         for i in range(len(lines)):
  176.             if lines[i][:1] == '\t':
  177.                 lines[i] = lines[i][1:]
  178.             elif lines[i][:self.indent_offset] == ' ' * self.indent_offset:
  179.                 lines[i] = lines[i][self.indent_offset:]
  180.         snippet = string.join(lines, '\r')
  181.         self.insert(snippet)
  182.         self.ted.WESetSelection(selstart, selstart + len(snippet))
  183.     
  184.     def domenu_shiftright(self):
  185.         self.expandselection()
  186.         selstart, selend = self.get_selection()
  187.         snippet = self.getselectedtext()
  188.         lines = string.split(snippet, '\r')
  189.         for i in range(len(lines) - (not lines[-1])):
  190.             lines[i] = self.tabbing(1) + lines[i]
  191.         snippet = string.join(lines, '\r')
  192.         self.insert(snippet)
  193.         self.ted.WESetSelection(selstart, selstart + len(snippet))
  194.  
  195. # Swap in PySpaceEditor for PyEditor
  196. Wtext.PyEditor = PySpaceEditor
  197. W.PyEditor = PySpaceEditor
  198.